unit io_imagecapture_basic;

{
  A very simple IO-system, it works by taking screenshoots of the game window.
  Does not implement the screenshoot function, since they are OS-specific.
}

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, basicIO, Graphics, SystemManager, types, controls, FileProvider,
  io_imagecapture_imgUtils, math, io_imagecapture_const, io_imagecapture_comparator,
  basicio_field, basicio_data, typinfo, basicio_city, basicio_player, basicio_street,
  basicio_const, basicio_resources;

type

  { TIO_ImageCapture_basic }
  TIO_ImageCapture_basic = class (TBasicIO)
    protected
      StatusComperator:   TImageComperator;
      ResourceComperator: TImageComperator;
    protected
      procedure AnalyzeRes(AImgInput: Graphics.TBitmap);
      procedure AnalyzeStatus(AImgInput: Graphics.TBitmap);
      procedure AnalyzeField(AImgInput: Graphics.TBitmap);
    protected
      procedure TakeScreenshoot(AImg: Graphics.TBitmap); virtual; abstract;
    public
      constructor Create; override;
      destructor Destroy; override;
    public
      procedure Update; override;
      procedure GatherInitiallyInfos; override;
  end;

implementation

uses
  StateMachine;

{ TIO_ImageCapture_basic }

constructor TIO_ImageCapture_basic.Create;
var
  lcSearch: TSearchRec;
  path: String;
  i: integer;
begin
  inherited Create;
  StatusComperator   := TImageComperator.Create;
  ResourceComperator := TImageComperator.Create;

  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\wait.bmp',           FT_Data),'GT_Wait');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\build_city.bmp',     FT_Data),'GT_Build_city');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\build_street.bmp',   FT_Data),'GT_Build_Street');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\trade_self.bmp',     FT_Data),'GT_Trade_Self');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\trade_enemy.bmp',    FT_Data),'GT_Trade_Enemy');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\build.bmp',          FT_Data),'GT_Build');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\thief_throwres.bmp', FT_Data),'GT_Thief_ThrowRes');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\thief_field.bmp',    FT_Data),'GT_Thief_Field');
  StatusComperator.LoadComparisonImage(GlobalSystemManager.FileProvider.ProvideFile('TurnStatusPics\thief_city.bmp',     FT_Data),'GT_Thief_City');

  path := GlobalSystemManager.FileProvider.ProvideFile('ResPics\', FT_Data);
  for i:=0 to 9 do
  begin
    If FindFirst(path+inttostr(i)+'\*',faAnyFile,lcSearch)=0 then
    begin
      repeat
        if lcSearch.Name[1] <> '.' then
          ResourceComperator.LoadComparisonImage(path+inttostr(i)+'\'+lcSearch.Name ,inttostr(i));
      until FindNext(lcSearch) <> 0;
      FindClose(lcSearch);
    end;
  end;
end;

destructor TIO_ImageCapture_basic.Destroy;
begin
  StatusComperator.free;
  ResourceComperator.free;
  inherited Destroy;
end;

procedure TIO_ImageCapture_basic.AnalyzeRes(AImgInput: Graphics.TBitmap);
var
  i: Integer;
  ownRes: TResources;
  ownPlayerRes: TPlayerResources;
  tmpImg: Tbitmap;

  procedure SetRes(AID, AValue: integer);
  begin
   case AID of
      0: ownRes.wood       := AValue;
      1: ownRes.dirt       := AValue;
      2: ownRes.sheeps     := AValue;
      3: ownRes.wheat      := AValue;
      4: ownRes.ore        := AValue;
      5: ownPlayerRes.knights           := AValue;
      6: ownPlayerRes.winpoints         := AValue;
      7: ownPlayerRes.monopolies        := AValue;
      8: ownPlayerRes.inventions        := AValue;
      9: ownPlayerRes.streets           := AValue;
   end;
  end;
begin
  tmpImg := TBitmap.Create;
  SingleColorImgEx(AImgInput,AImgInput,3317920);
  try
    for i := 0 to 9 do
    begin
      ExtractBitmapRect(AImgInput,tmpImg,ResCountRect[i]);
      SetRes(i, StrToInt(ResourceComperator.CompareImage(tmpImg)));
    end;
  finally
  tmpImg.Free;
  end;
  data.Player[GlobalSystemManager.AI.AIPlayerID].Resources := ownRes;
  data.Player[GlobalSystemManager.AI.AIPlayerID].PlayerResources := ownPlayerRes;
end;

procedure TIO_ImageCapture_basic.AnalyzeStatus(AImgInput: Graphics.TBitmap);
  function GetDiceCount: Integer;
    function isYellow(AColor: TColor): boolean;
    begin
      //returns true if AColor is near the searched "yellow"
      result := InRange(blue(AColor),90,150) and InRange(red(AColor),215,255) and InRange(green(AColor),215,355);
    end;
    function CheckMark(AMark: TPoint): boolean;
    begin
      //returns true if the dice got a mark at the position to check
      result := isYellow(AImgInput.Canvas.Pixels[AMark.x,AMark.y]);
    end;
    var
      i: integer;
  begin
    //Check all marks and find the corresponding dice value
    result := 0;
    for i := 0 to 1 do
    begin
      if CheckMark(DiceMarkPoint[i][0]) then    //see "infos\würfel.png" WARNING: modified
        //3,4,5,6
        if CheckMark(DiceMarkPoint[i][1]) then
          if CheckMark(DiceMarkPoint[i][3]) then
            result := result + 5
          else
            result := result + 3
        else
          //4,6
          if CheckMark(DiceMarkPoint[i][2]) then
            result := result + 6
          else
            result := result + 4
      else
        //1,2
        if CheckMark(DiceMarkPoint[i][1]) then
          result := result + 1
        else
          result := result + 2
    end;
  end;
  function GetTurnStatus: TGameTurn;
  var
    tmpImg: TBitmap;
  begin
    //gets current status of your turn (build, trade, etc.)
    tmpImg := TBitmap.Create;
    try
      ExtractBitmapRect(AImgInput,tmpImg,TurnStatusRect);
      result := TGameTurn( GetEnumValue( TypeInfo(TGameTurn),StatusComperator.CompareImage(tmpImg) ) );
    finally
      tmpImg.Free;
    end;
  end;
begin
  //for now just read the dice values
  Data.Dice := GetDiceCount;
  Data.Turn := GetTurnStatus;
end;

procedure TIO_ImageCapture_basic.AnalyzeField(AImgInput: Graphics.TBitmap);
  procedure GetResKinds;
  var
    i, Value: integer;
    Field: TField;
  begin
    for i:=0 to high(FieldResRectArray) do
    begin
      //get the avg. color in an area of each field
      value := AvgColorInRect(AImgInput,rect(FieldResRectArray[i].PixelX,
                                             FieldResRectArray[i].PixelY,
                                             FieldResRectArray[i].PixelX+FieldResRectWidth,
                                             FieldResRectArray[i].PixelY+FieldResRectHeight));
      Field := Data.Map.FieldsByInt[FieldResRectArray[i].FieldX,FieldResRectArray[i].FieldY];
      //depending on the color, set field kind.
      case CompareColorsEx(value,[ColorWood,ColorDirt,ColorSheeps,ColorWheat,ColorOre, ColorDesert]) of
        0: Field.kind := RK_Wood;
        1: Field.kind := RK_Dirt;
        2: Field.kind := RK_Sheeps;
        3: Field.kind := RK_Wheat;
        4: Field.kind := RK_Ore;
        5: begin
             //found the thief and desert
             Field.kind := PK_None;
             Data.Map.SetThiefPosition(FieldResRectArray[i].FieldX,FieldResRectArray[i].FieldY);
           end;
      end;
    end;
  end;
  procedure GetFieldDices;
  var
    i: integer;
    Field: TField;
    tmpimg: TBitmap;
    comperator: TImageComperator;
    lcSearch: TSearchRec;
    path: String;
  begin
    //load all images to compare
    comperator := TImageComperator.Create;
    path := GlobalSystemManager.FileProvider.ProvideFile('DicePics\', FT_Data);
    If FindFirst(path+'*',faAnyFile,lcSearch)=0 then begin
      repeat
        if lcSearch.Name[1] <> '.' then
          comperator.LoadComparisonImage(path+lcSearch.Name ,lcSearch.Name[1]+lcSearch.Name[2]);
      until FindNext(lcSearch) <> 0;
      FindClose(lcSearch);
    end;
    //for each field
    try
      for i:=0 to high(FieldResRectArray) do
      begin
        Field := Data.Map.FieldsByInt[FieldResRectArray[i].FieldX,FieldResRectArray[i].FieldY];
        //if the field is not the desert
        If Field.kind = PK_None then continue;

        //extract the nummber in a rect
        tmpimg := TBitmap.Create;
        ExtractBitmapRect(AImgInput, tmpimg ,rect(FieldResRectArray[i].PixelX+FieldDiceOffsetWidth,
                                                  FieldResRectArray[i].PixelY+FieldDiceOffsetHeight,
                                                  FieldResRectArray[i].PixelX+FieldDiceWidth+FieldDiceOffsetWidth,
                                                  FieldResRectArray[i].PixelY+FieldDiceHeight+FieldDiceOffsetHeight));
        //make it a black/white image
        SingleColorImg(tmpimg, tmpimg);
        //compare with other images to find the nummber
        Field.dice :=  strToInt(comperator.CompareImage(tmpimg));
        tmpimg.free;
      end;

    finally
      comperator.Free;
    end;

  end;
  procedure GetHarbors;
    function IntToHarborRes(AHarborKind: integer): TResKind;
    begin
      case AHarborKind of
        0:
          begin
            result := RK_Wood;
          end;
        1:
          begin
            result := RK_Dirt;
          end;
        2:
          begin
            result := RK_Sheeps;
          end;
        3:
          begin
            result := RK_Wheat;
          end;
        4:
          begin
            result := RK_Ore ;
          end;
        5:
          begin
            result := PK_None;
          end;
      end;
    end;

  var
    i,j,highestAmount,HarborKind: integer;
    Colors: TColorIntegerArray;
  begin
    for i := 0 to 8 do
    begin
      highestAmount := 0;
      HarborKind := -1;
      CountColorPixelEx(AImgInput,HarborKindRect[i],HarborKindColor,5,Colors);
      for j:=0 to high(Colors) do
      begin
        if (Colors[j] > highestAmount) then
        begin
          highestAmount := Colors[j];
          harborKind := j;
        end;
        if (harborKind =-1) then
        begin
           harborKind := 5;
        end;
      end;
      Data.Map.Harbor[i].resource := IntToHarborRes(harborKind);
    end;
  end;

  procedure GetStreets;
  var
    i,j: integer;
    playerColor: TPlayerColor;
    street: TStreet;
    streetPos: TStreetPosition;
    posA: TPoint;
    posB: TPoint;
    Colors: TColorIntegerArray;
    highestAmount: integer;
    highestColor: integer;

  begin
    Data.Map.ResetStreets;
    for i:=low(StreetPosArray) to high(StreetPosArray) do
    begin
      // for every possible street position extract avg. color
      posA.x:= StreetPointArray[i].PixelX-3;
      posA.y:= StreetPointArray[i].PixelY-3;
      posB.x:= posA.x + 8;
      posB.y:= posA.y + 8;

      CountColorPixel(AImgInput,rect(PosA.x, PosA.y, PosB.x, PosB.y),[clWhite,clRed,51455,clBlue], Colors);

      highestAmount := 0;
      highestColor := -1;
      for j:=0 to high(Colors) do
      begin
        if Colors[j] > highestAmount then
        begin
          highestAmount := Colors[j];
          highestColor := j;
        end;
      end;
      if highestAmount < 5 then
      begin
        highestColor := -1;
      end;

      //get owner of current street, if none, analyse next
      case highestColor of
        -1: continue;
        0: begin playerColor:= PC_White; end;
        1: begin playerColor:= PC_Red; end;
        2: begin playerColor:= PC_Yellow; end;
        3: begin playerColor:= PC_Blue; end;
      end;
      //insert found street in virtuell playfield
      streetPos[0]:=StreetPosArray[i][0];
      streetPos[1]:=StreetPosArray[i][1];
      street:= TStreet.create(streetPos,Data.GetPlayerByColor(playerColor));
      Data.Map.AddStreet(street);
    end;
  end;

  procedure GetCities;
  var
    i: integer;
    cityColor: Tcolor;
    city: TCity;
    isExtended: boolean;
    playerColor: TPlayerColor;
    cityPos: TcityPosition;

    function AnalyzeColor: boolean;
    var
      j,x:integer;
      minValue,Value: integer;
    begin
      result := false;
      minValue := MAXINT;
      for j:=0 to high(CityColors) do
      begin
        Value := CompareColorsValue(cityColor,CityColors[j].Color);
        if Value < MinValue then
        begin
          MinValue := value;
          x := j;
        end;
      end;
      if MinValue > 20 then
      begin
        result := false;
      end else
      begin
        playerColor := CityColors[x].Player;
        isExtended  := CityColors[x].Extended;
        result := true;
      end;
    end;
  begin
    Data.Map.ResetCities;
    // for each possible city position
    for i:=low(CityPosArray) to high(CityPosArray) do
    begin
     // extract avg color
     cityColor := AvgColorInRect(AImgInput,rect(CityPosArray[i].PixelX-8,
                                                CityPosArray[i].PixelY-7,
                                                CityPosArray[i].PixelX+10,
                                                CityPosArray[i].PixelY+10));


     //get owner and kind of current city, if none, analyse next
     if not AnalyzeColor then continue;
     //insert found city in virtuell playfield
     cityPos.x := CityPosArray[i].CityX;
     cityPos.y := CityPosArray[i].CityY;
     city := Tcity.create(cityPos,Data.GetPlayerByColor(playerColor));
     city.isExtended := isExtended;
     Data.Map.AddCity(city);
    end;
  end;
  procedure GetThiefPosition;
     var
      i: integer;
      fieldcolor: Tcolor;
    begin
       for i:=0 to high(FieldResRectArray) do
        begin
          //extrace the collor in the nummber circle
          fieldcolor := AvgColorInRect(AImgInput,rect(FieldResRectArray[i].PixelX+FieldDiceOffsetWidth,
                                                      FieldResRectArray[i].PixelY+FieldDiceOffsetHeight,
                                                      FieldResRectArray[i].PixelX+FieldDiceWidth+FieldDiceOffsetWidth,
                                                      FieldResRectArray[i].PixelY+FieldDiceHeight+FieldDiceOffsetHeight));
          if CompareColors(fieldcolor,ColorThief,20) then
          begin
             Data.Map.SetThiefPosition(FieldResRectArray[i].FieldX,FieldResRectArray[i].FieldY);
          end;
        end;
    end;
begin
  inherited;
  if GlobalStateMachine.State in [SS_InitializeGame] then
  begin
    GetResKinds;
    GetFieldDices;
    GetHarbors;
  end;
  if GlobalStateMachine.State in [SS_AnalyzeGameState] then
  begin
    GetStreets;
    GetCities;
    GetThiefPosition;
  end;
end;

procedure TIO_ImageCapture_basic.Update;
var
   FullImg, FieldImg, ChatImg, ResImg, BuildImg, TradeImg, StatusImg: Graphics.TBitmap;
   debugPrefix, debugPath: String;
begin
  inherited;
  FullImg   := Graphics.TBitmap.Create;
  FieldImg  := Graphics.TBitmap.Create;
  ChatImg   := Graphics.TBitmap.Create;
  ResImg    := Graphics.TBitmap.Create;
  BuildImg  := Graphics.TBitmap.Create;
  TradeImg  := Graphics.TBitmap.Create;
  StatusImg := Graphics.TBitmap.Create;
  try
    TakeScreenshoot(FullImg);

    ExtractBitmapRect(FullImg,FieldImg,FieldRect);         //find the field and save it
    ExtractBitmapRect(FullImg,ChatImg,ChatRect);           //find the chat and save it
    ExtractBitmapRect(FullImg,ResImg,ResRect);             //find our res and save it
    ExtractBitmapRect(FullImg,BuildImg,BuildRect);         //find our build menu and save it
    ExtractBitmapRect(FullImg,TradeImg,TradeRect);         //find the trade menu and save it
    ExtractBitmapRect(FullImg,StatusImg,StatusRect);       //find the status

    AnalyzeRes(ResImg);
    AnalyzeStatus(StatusImg);
    AnalyzeField(FieldImg);


    //debug stuff
    if false then
    begin
      debugprefix := FormatDateTime('hh-nn-ss.zzz', now)+'_';
      debugPath   := GlobalSystemManager.FileProvider.ProvideFile('.\debug img\', FT_DebugOutput);
      if not FileExists(debugPath) then CreateDir(debugPath);

      ResImg.SaveToFile(debugPath+debugprefix+'ResImg.bmp');
      FieldImg.SaveToFile(debugPath+debugprefix+'FieldImg.bmp');
      ChatImg.SaveToFile(debugPath+debugprefix+'ChatImg.bmp');
      BuildImg.SaveToFile(debugPath+debugprefix+'BuildImg.bmp');
      TradeImg.SaveToFile(debugPath+debugprefix+'TradeImg.bmp');
      StatusImg.SaveToFile(debugPath+debugprefix+'StatusImg.bmp');
      FullImg.SaveToFile(debugPath+debugprefix+'FullImg.bmp');
    end;
  finally
    StatusImg.Free;
    TradeImg.Free;
    BuildImg.Free;
    ResImg.Free;
    ChatImg.Free;
    FieldImg.Free;
    FullImg.Free;
  end;
end;

procedure TIO_ImageCapture_basic.GatherInitiallyInfos;
var
   FullImg, FieldImg, ChatImg: Graphics.TBitmap;
begin
  inherited;
  FullImg   := Graphics.TBitmap.Create;
  FieldImg  := Graphics.TBitmap.Create;
  ChatImg   := Graphics.TBitmap.Create;
  try
    TakeScreenshoot(FullImg);

    ExtractBitmapRect(FullImg,FieldImg,FieldRect);         //find the field and save it
    ExtractBitmapRect(FullImg,ChatImg,ChatRect);           //find the chat and save it

    AnalyzeField(FieldImg);
  finally
    ChatImg.Free;
    FieldImg.Free;
    FullImg.Free;
  end;

end;

end.


